Skip to content

[US-15.2 / OBT-250] Public request page for languages and projects - #36

Open
levigtri wants to merge 7 commits into
mainfrom
levigft/obt-250-us-152-frontend-public-request-page
Open

[US-15.2 / OBT-250] Public request page for languages and projects#36
levigtri wants to merge 7 commits into
mainfrom
levigft/obt-250-us-152-frontend-public-request-page

Conversation

@levigtri

@levigtri levigtri commented Jul 10, 2026

Copy link
Copy Markdown
Member

[US-15.2 / OBT-250] Public request page for languages and projects

Summary

Adds a public page at /request (no authentication, outside the AppShell) where a visitor can request the creation of a Language or a Project by providing only their name and email plus the same fields as the authenticated create dialogs. Submissions go to the new unauthenticated /api/public endpoints (backend PR shemaobt/shema-api#102) and are stored as pending requests for admin review. Abuse protection: the backend rate-limits by IP, and a Google reCAPTCHA v2 checkbox is rendered when VITE_RECAPTCHA_SITE_KEY is set — unset, the captcha is disabled and the app runs with rate limiting alone (matching the backend's optional RECAPTCHA_SECRET_KEY). The project form's language field is a searchable combobox (type to filter by name or code) listing the languages already registered, with a pinned "Create a new language" option that reveals inline name/code fields — mirroring the authenticated flow. Duplicate checks cover both name and code, case-insensitively ("English/eng" ≡ "english/ENG"), client-side with inline errors and server-side via 409. The page follows the LoginPage visual pattern and Shema design tokens, reusing the existing ui primitives.

Changes

  1. src/types/publicRequest.ts (+ re-export in src/types/index.ts) — PublicLanguageOption, PublicLanguageRequestCreate, PublicProjectRequestCreate, PublicRequestResponse, mirroring the backend schemas.
  2. src/services/api.ts — new publicAPI namespace: listLanguages, requestLanguage, requestProject against /api/public/*. Reuses the single axios instance (public endpoints never return 401, so the refresh/redirect interceptor cannot fire for anonymous visitors).
  3. src/components/common/ReCaptcha.tsx — reusable reCAPTCHA v2 checkbox. Injects the Google api.js script (explicit render) only when the component mounts, so no other page loads it. Site key from VITE_RECAPTCHA_SITE_KEY; when unset, exports RECAPTCHA_ENABLED = false and renders nothing — forms then submit without a token. Emits the token via onChange; expiry/error clear it. Remount with a new key to reset after a failed submit (tokens are single-use).
  4. src/components/pages/publicRequest/index.tsx — the page: requester name + email (shared), Project/Language tabs, success state with "Submit another request", footer link back to /login. Fetches the public languages list once for both tabs.
  5. src/components/pages/publicRequest/LanguageCombobox.tsx — searchable combobox built on the existing Popover + Input primitives: type to filter languages by name or code, pinned "Create a new language" entry, check indicator on the selected item.
  6. src/components/pages/publicRequest/LanguageRequestForm.tsx — name + 3-letter ISO 639-3 code; blocks submission with inline errors when the name or the code already exists (case-insensitive); toasts surface the backend detail for 409/400 and handle 429/422 explicitly.
  7. src/components/pages/publicRequest/ProjectRequestForm.tsx — name, optional description, and the language combobox; choosing "Create a new language" reveals inline name/code fields with the same case-insensitive duplicate checks; failed languages fetch shows an error with a Retry button.
  8. src/App.tsx — top-level /request route, sibling of /login, outside the AppShell (no auth guard).
  9. src/components/pages/LoginPage.tsx — discreet "Request a new project or language" link in the footer.
  10. src/vite-env.d.ts / .env.example — typing and documentation for VITE_RECAPTCHA_SITE_KEY.
  11. Review hardening — visitors with stale/expired tokens are no longer hard-redirected from /request to /login by the axios 401 interceptor (public-path guard in api.ts); the reCAPTCHA loader detects script failures (onerror + poll timeout) and shows a recoverable message; tab switches preserve form state and the solved captcha (forceMount + data-[state=inactive]:hidden).

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix
  • Breaking change
  • Refactor / chore

Testing

  1. npm run typecheck, npm run lint (0 errors; only the pre-existing ProjectsPage warning) and npm run build pass.
  2. With the backend branch of [US-15.1 / OBT-250] Public creation requests for languages and projects shema-api#102 running locally:
    1. Open /request in a private window (no tokens in localStorage) — the page renders and never redirects to /login.
    2. The Project tab's language combobox lists the registered languages, filters as you type (by name or code), and offers "Create a new language" with inline fields; duplicate name/code (any casing) shows inline errors and blocks submit.
    3. Fill requester name/email, project fields, tick the captcha — submit succeeds and shows the success card with the informed email.
    4. On the Language tab, type the code of an existing language — inline error appears and submit stays disabled; a new 3-char code submits successfully; submitting the same code again returns 409 with a toast.
    5. Submit button stays disabled until requester name, valid email, form fields and (when the captcha is enabled) the captcha token are all present.
    6. Without VITE_RECAPTCHA_SITE_KEY: no captcha widget appears and submissions succeed; rapid repeated submissions surface the 429 toast.

levigtri and others added 7 commits July 10, 2026 14:03
Types mirror the new unauthenticated backend endpoints under
/api/public: slim language options for the combobox and creation
request payloads carrying requester name/email and the reCAPTCHA
token. publicAPI reuses the existing axios instance since public
endpoints never return 401.
New /request route outside the authenticated shell. Visitors provide
name and email and submit either a language request (blocked when the
3-char code already exists in the system) or a project request whose
language combobox lists languages already registered, via the public
endpoints. ReCaptcha renders the v2 checkbox on demand, injecting the
Google script only on this page; the site key comes from
VITE_RECAPTCHA_SITE_KEY with Google's test key as dev fallback.
Gives the /request page an entry point for visitors who land on the
login screen without an account.
Without VITE_RECAPTCHA_SITE_KEY the captcha widget is not rendered and
the token is omitted from the payload, matching the backend behavior
when RECAPTCHA_SECRET_KEY is unset — the public endpoints then rely on
per-IP rate limiting alone. Forms surface 429 responses with a
dedicated toast.
The project form's language field becomes a searchable combobox
(type to filter by name or code) with a pinned 'Create a new language'
option that reveals inline name/code fields, mirroring the
authenticated flow. Duplicate checks now cover BOTH name and code,
case-insensitively, with inline errors on the language tab and on the
new-language fields; API error toasts surface the backend detail for
409/400 and handle 422 explicitly.
Visitors with stale tokens are no longer hard-redirected from public
paths to /login when the session restore 401s. The reCAPTCHA loader
reports script failures (onerror + poll timeout) instead of silently
dead-ending the form. A failed languages fetch shows an explicit error
with a Retry button instead of an empty-state message, and tab
switches keep form state and captcha via forceMount + hidden.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@levigtri, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: e6a11ee0-f59f-40e6-865a-eeef21a710c7

📥 Commits

Reviewing files that changed from the base of the PR and between 130fc4a and e2045b9.

📒 Files selected for processing (12)
  • .env.example
  • src/App.tsx
  • src/components/common/ReCaptcha.tsx
  • src/components/pages/LoginPage.tsx
  • src/components/pages/publicRequest/LanguageCombobox.tsx
  • src/components/pages/publicRequest/LanguageRequestForm.tsx
  • src/components/pages/publicRequest/ProjectRequestForm.tsx
  • src/components/pages/publicRequest/index.tsx
  • src/services/api.ts
  • src/types/index.ts
  • src/types/publicRequest.ts
  • src/vite-env.d.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch levigft/obt-250-us-152-frontend-public-request-page

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant